package com.hynet.customer.client.common.sm2;


import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.util.encoders.Hex;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Enumeration;

public class Sm2Util {

    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    private static final String ALGO_NAME_EC = "EC";
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);

    //////////////////////////////////////////////////////////////////////////////////////

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());


    private final static int RS_LEN = 32;
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 生成sm3摘要
     *
     * @param src
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String generateSM3HASH(String src) throws UnsupportedEncodingException {
        byte[] md = new byte[32];
        byte[] msg1 = src.getBytes("utf-8");
        SM3Digest sm3 = new SM3Digest();
        sm3.update(msg1, 0, msg1.length);
        sm3.doFinal(md, 0);
        String s = new String(Hex.encode(md)).toUpperCase();
        return s;
    }

    public static SignVO signSm3WithSm2(String msg, String userId, String privateKey) throws Exception {
        SignVO signVO = new SignVO();
        BCECPrivateKey bcecPrivateKey = getPrivatekeyFromD(privateKey);
        byte[] sig = signSm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), bcecPrivateKey);
        signVO.setBase64Sign(Base64.encodeBase64String(sig));
        signVO.setHexSign(Hex.toHexString(sig));
        return signVO;
    }

    public static SignVO signSm3WithSm2(String msg, String userId, PrivateKey privateKey) throws Exception {
        SignVO signVO = new SignVO();
        byte[] sig = signSm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), privateKey);
        signVO.setBase64Sign(Base64.encodeBase64String(sig));
        signVO.setHexSign(Hex.toHexString(sig));
        return signVO;

    }

    public static BCECPrivateKey getPrivatekeyFromD(String privateKey) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(privateKey, 16), ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return r||s，直接拼接byte数组的rs
     */
    public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) {
        return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey));
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return rs in <b>asn1 format</b>
     */
    public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature signer = Signature.getInstance("SM3withSM2", "BC");
            signer.setParameter(parameterSpec);
            signer.initSign(privateKey, new SecureRandom());
            signer.update(msg, 0, msg.length);
            byte[] sig = signer.sign();
            return sig;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * BC的SM3withSM2签名得到的结果的rs是asn1格式的，这个方法转化成直接拼接r||s
     *
     * @param rsDer rs in asn1 format
     * @return sign result in plain byte array
     */
    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[RS_LEN * 2];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, RS_LEN, s.length);
        return result;
    }

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
        // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and have length<=32
        byte[] rs = rOrS.toByteArray();
        if (rs.length == RS_LEN) {
            return rs;
        } else if (rs.length == RS_LEN + 1 && rs[0] == 0) {
            return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        } else if (rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte) 0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }


    /**
     * @param msg
     * @param userId
     * @param rs        签名
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(String msg, String userId, String rs, String publicKey) throws Exception {
        return verifySm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), Base64.decodeBase64(rs),
                getBCECPublicKeyFromD(publicKey));
    }

    /**
     * @param msg
     * @param userId
     * @param rs        签名
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(String msg, String userId, String rs, PublicKey publicKey) throws Exception {
        return verifySm3WithSm2(msg.getBytes("utf-8"), userId.getBytes("utf-8"), Base64.decodeBase64(rs), publicKey);
    }

    /**
     * @param msg
     * @param userId
     * @param rs        r||s，直接拼接byte数组的rs
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey);
    }

    /**
     * BC的SM3withSM2验签需要的rs是asn1格式的，这个方法将直接拼接r||s的字节数组转化成asn1格式
     *
     * @param sign in plain byte array
     * @return rs result in asn1 format
     */
    private static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
        if (sign.length != RS_LEN * 2) {
            throw new RuntimeException("err rs. ");
        }
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        try {
            return new DERSequence(v).getEncoded("DER");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param msg
     * @param userId
     * @param rs        in <b>asn1 format</b>
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature verifier = Signature.getInstance("SM3withSM2", new BouncyCastleProvider());
            verifier.setParameter(parameterSpec);
            verifier.initVerify(publicKey);
            verifier.update(msg, 0, msg.length);
            return verifier.verify(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 转成公钥对象
     *
     * @param pubKey
     * @return
     */
    public static PublicKey getBCECPublicKeyFromD(String pubKey) throws Exception {
        //TODO 不知道为什么在公钥前加04才不会有问题
        final String prefix = "04";
        if (!pubKey.startsWith(prefix)) {
            pubKey = prefix + pubKey;
        }
        // 将公钥HEX字符串转换为椭圆曲线对应的点
        ECPoint ecPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
        ECPublicKeySpec eCPublicKeySpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
        return new BCECPublicKey("EC", eCPublicKeySpec, BouncyCastleProvider.CONFIGURATION);


    }


//    /**
//     * 国密公私钥生成
//     *
//     * @throws NoSuchAlgorithmException
//     * @throws InvalidAlgorithmParameterException
//     */
//    @Test
//    public void test11() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
//        // 获取SM2椭圆曲线的参数
//        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
//        // 获取一个椭圆曲线类型的密钥对生成器
//        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
//        // 使用SM2参数初始化生成器
//        kpg.initialize(sm2Spec);
//
//        // 使用SM2的算法区域初始化密钥生成器
//        kpg.initialize(sm2Spec, new SecureRandom());
//        // 获取密钥对
//        KeyPair keyPair = kpg.generateKeyPair();
//
//        log.info(Hex.toHexString(((BCECPrivateKey) keyPair.getPrivate()).getD().toByteArray()));
//        log.info(Hex.toHexString(((BCECPublicKey) keyPair.getPublic()).getQ().getEncoded(false)));
//    }

    public static byte[] encrypt(byte[] plainText, byte[] key) throws Exception {
        try {
            X509EncodedKeySpec eks = new X509EncodedKeySpec(key);
            KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            PublicKey publicKey = kf.generatePublic(eks);
            return encrypt((BCECPublicKey) publicKey, plainText);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }


    public static byte[] decrypt(byte[] cipherText, byte[] key) throws Exception {
        try {
            PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(key);
            KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            PrivateKey privateKey = kf.generatePrivate(peks);
            return decrypt((BCECPrivateKey) privateKey, cipherText);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }


    public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException,
            NoSuchProviderException {
        InputStream is = null;
        try {
            is = new FileInputStream(certFilePath);
            return getX509Certificate(is);
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    public static X509Certificate getX509Certificate(byte[] certBytes) throws CertificateException,
            NoSuchProviderException {
        ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
        return getX509Certificate(bais);
    }

    public static X509Certificate getX509Certificate(InputStream is) throws CertificateException,
            NoSuchProviderException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return (X509Certificate) cf.generateCertificate(is);
    }

    public static BCECPublicKey getBCECPublicKey(X509Certificate sm2Cert) {
        ECPublicKey pubKey = (ECPublicKey) sm2Cert.getPublicKey();
        ECPoint q = pubKey.getQ();
        ECParameterSpec parameterSpec = new ECParameterSpec(CURVE, G_POINT,
                SM2_ECC_N, SM2_ECC_H);
        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(q, parameterSpec);
        return new BCECPublicKey(pubKey.getAlgorithm(), pubKeySpec,
                BouncyCastleProvider.CONFIGURATION);
    }

    public static byte[] readFile(String filePath) throws IOException {
        RandomAccessFile raf = null;
        byte[] data;
        try {
            raf = new RandomAccessFile(filePath, "r");
            data = new byte[(int) raf.length()];
            raf.read(data);
            return data;
        } finally {
            if (raf != null) {
                raf.close();
            }
        }
    }

    public static BCECPrivateKey convertSEC1ToECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key);
        return privateKey;
    }

    public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key));
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }

    public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException {
        /**
         * 参考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
         * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey，逆向拼装
         */
        X962Parameters params = getDomainParametersFromName(JDK_EC_SPEC, false);
        ASN1OctetString privKey = new DEROctetString(sec1Key);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(0)); //版本号
        v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); //算法标识
        v.add(privKey);
        DERSequence ds = new DERSequence(v);
        return ds.getEncoded(ASN1Encoding.DER);
    }

    public static X9ECParameters getDomainParametersFromName(String curveName) {
        X9ECParameters domainParameters;
        try {
            if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
                ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
                domainParameters = ECUtil.getNamedCurveByOid(oidID);
            } else {
                if (curveName.indexOf(' ') > 0) {
                    curveName = curveName.substring(curveName.indexOf(' ') + 1);
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                } else {
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                }
            }
        } catch (IllegalArgumentException ex) {
            domainParameters = ECUtil.getNamedCurveByName(curveName);
        }
        return domainParameters;
    }

    public static X962Parameters getDomainParametersFromName(
            java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
        X962Parameters params;

        if (ecSpec instanceof ECNamedCurveSpec) {
            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
            if (curveOid == null) {
                curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
            }
            params = new X962Parameters(curveOid);
        } else if (ecSpec == null) {
            params = new X962Parameters(DERNull.INSTANCE);
        } else {
            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());

            X9ECParameters ecP = new X9ECParameters(
                    curve,
                    new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
                    ecSpec.getOrder(),
                    BigInteger.valueOf(ecSpec.getCofactor()),
                    ecSpec.getCurve().getSeed());

            //// 如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码，因为高版本的EC5Util.convertPoint没有向下兼容
            /*
            X9ECParameters ecP = new X9ECParameters(
                curve,
                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
                ecSpec.getOrder(),
                BigInteger.valueOf(ecSpec.getCofactor()),
                ecSpec.getCurve().getSeed());
            */

            params = new X962Parameters(ecP);
        }

        return params;
    }

    /**
     * string类型的公钥转换成公钥对象
     *
     * @param pubKey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchProviderException
     */
    public static PublicKey string2PublicKey(String pubKey) throws Exception {
        try {
            byte[] x509Bytes = Encodes.decodeBase64(pubKey);
            X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
            KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            return kf.generatePublic(eks);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }

    /**
     * string类型的私钥转换后私钥对象
     *
     * @param priKey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchProviderException
     */
    public static PrivateKey string2PrivateKey(String priKey) throws Exception {
        try {
            byte[] pkcs8Key = Encodes.decodeBase64(priKey);
            PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
            KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            return kf.generatePrivate(peks);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }

    /**
     * @param pubKey  公钥
     * @param srcData 原文
     * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
        ECParameterSpec parameterSpec = pubKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        ECPublicKeyParameters pubKeyParameters = new ECPublicKeyParameters(pubKey.getQ(), domainParameters);
        return encrypt(SM2Engine.Mode.C1C3C2, pubKeyParameters, srcData);
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(SM2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    /**
     * @param priKey    私钥
     * @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
        ECParameterSpec parameterSpec = priKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        ECPrivateKeyParameters priKeyParameters = new ECPrivateKeyParameters(priKey.getD(), domainParameters);
        return decrypt(SM2Engine.Mode.C1C3C2, priKeyParameters, sm2Cipher);
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKeyParameters 私钥
     * @param sm2Cipher        根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParameters);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    public static void main(String[] args) {

        String publicKeyBase64 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEIjBMLKww86I17o08YulBWS6kIPLpJWw+PVRJtW41wPQsZJi8vz7/oC0q7+0wOzsp3Cg+uZ5/08zZxZzVgYpmsg==";
        String privateKeyBase64 = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg5HGA2YSHIwsTNAkXecwCRA0mgX1sGOUfnvStx5jCR0KgCgYIKoEcz1UBgi2hRANCAAQiMEwsrDDzojXujTxi6UFZLqQg8uklbD49VEm1bjXA9CxkmLy/Pv+gLSrv7TA7OyncKD65nn/TzNnFnNWBimay";

        //国密 公钥
        String strPubKey = "22304c2cac30f3a235ee8d3c62e941592ea420f2e9256c3e3d5449b56e35c0f42c6498bcbf3effa02d2aefed303b3b29dc283eb99e7fd3ccd9c59cd5818a66b2";
        //国密 私钥
        String strPrivateKey = "e47180d98487230b1334091779cc02440d26817d6c18e51f9ef4adc798c24742";
        //国密 userId
        String strUserId = "1234567812345678";
        //待签名内容
        String strBody = "I Love You";

        try {
            //签名值
            //base64 私钥签名
            SignVO signVO = signSm3WithSm2(strBody, strUserId, string2PrivateKey(privateKeyBase64));
            System.out.println("使用base64私钥签名,hex签名=" + signVO.getHexSign());
            System.out.println("使用base64私钥签名,base64签名=" + signVO.getBase64Sign());
            boolean bRst = verifySm3WithSm2(strBody, strUserId, signVO.getBase64Sign(), string2PublicKey(publicKeyBase64));
            System.out.println("使用base64公钥验证 base64签名：" + bRst);
            bRst = verifySm3WithSm2(strBody.getBytes(StandardCharsets.UTF_8), strUserId.getBytes(StandardCharsets.UTF_8), Hex.decode(signVO.getHexSign()), string2PublicKey(publicKeyBase64));
            System.out.println("使用base64公钥验证 hex签名：" + bRst);
            String netSign = "qbgJ622U3zu1V4sF9f269pNVQ6eL+hI7veUMKXxNEWUvsrWl/cx6J0aVYEct+R9P15vSodRNhXv1FoooeSwonA==";
            bRst = verifySm3WithSm2(strBody, strUserId, netSign, string2PublicKey(publicKeyBase64));
            System.out.println("使用base64公钥验证net base64签名：" + bRst);
            bRst = verifySm3WithSm2(strBody.getBytes(StandardCharsets.UTF_8), strUserId.getBytes(StandardCharsets.UTF_8), Hex.decode("751610c46702cb78f27f704247794317a143ef520e26875a219cf1c9b9521ebede4c85d5f13f9a2cde9d0065e672510b2955f81c8e6c05305bbd521513c87e04"), string2PublicKey(publicKeyBase64));
            System.out.println("使用base64公钥验证 net hex签名：" + bRst);
            //hex私钥签名
            signVO = signSm3WithSm2(strBody, strUserId, strPrivateKey);
            System.out.println("使用hex私钥签名,hex签名=" + signVO.getHexSign());
            System.out.println("使用hex私钥签名,base64签名=" + signVO.getBase64Sign());
            bRst = verifySm3WithSm2(strBody, strUserId, signVO.getBase64Sign(), strPubKey);
            System.out.println("使用hex公钥验证 base64签名：" + bRst);
            bRst = verifySm3WithSm2(strBody.getBytes(StandardCharsets.UTF_8), strUserId.getBytes(StandardCharsets.UTF_8), Hex.decode(signVO.getHexSign()), getBCECPublicKeyFromD(strPubKey));
            System.out.println("使用hex公钥验证 hex签名：" + bRst);
            netSign = "qbgJ622U3zu1V4sF9f269pNVQ6eL+hI7veUMKXxNEWUvsrWl/cx6J0aVYEct+R9P15vSodRNhXv1FoooeSwonA==";
            bRst = verifySm3WithSm2(strBody, strUserId, netSign, strPubKey);
            System.out.println("使用hex公钥验证net base64签名：" + bRst);
            bRst = verifySm3WithSm2(strBody.getBytes(StandardCharsets.UTF_8), strUserId.getBytes(StandardCharsets.UTF_8), Hex.decode("751610c46702cb78f27f704247794317a143ef520e26875a219cf1c9b9521ebede4c85d5f13f9a2cde9d0065e672510b2955f81c8e6c05305bbd521513c87e04"), getBCECPublicKeyFromD(strPubKey));
            System.out.println("使用hex公钥验证 net hex签名：" + bRst);

            //使用base64公钥加密
            byte[] encrypt = encrypt(strBody.getBytes(StandardCharsets.UTF_8), Encodes.decodeBase64(publicKeyBase64));
            String base64Encrypt = Encodes.encodeBase64(encrypt);
            System.out.println("使用base64公钥加密，base64密文=" + base64Encrypt);
            System.out.println("使用base64公钥加密，hex密文=" + Hex.toHexString(encrypt));
            byte[] decryByte = decrypt(Encodes.decodeBase64(base64Encrypt), Encodes.decodeBase64(privateKeyBase64));
            System.out.println("使用base64私钥解base64密文=" + new String(decryByte));
            decryByte = decrypt(Hex.decode(Hex.toHexString(encrypt)), Encodes.decodeBase64(privateKeyBase64));
            System.out.println("使用base64私钥解hex密文=" + new String(decryByte));
            //使用base64私钥 解密 base64 密文
            String baseEncrypt = "BLgKi+y39r1J6+BHbrKXu2hdmBzyjVTSzNAxPwyLhZZH6gdes67B/O5c5J3rqS+cmGFnvoxsNTgi8d5GrQmlFEB2hl9LQipNU4BxRiC4wlFULOZpCetT/G/xQGRw1oli+rwgi3r8RIOH5hk=";
            decryByte = decrypt(Encodes.decodeBase64(baseEncrypt), Encodes.decodeBase64(privateKeyBase64));
            System.out.println("使用base64私钥解密，net base64密文=" + new String(decryByte));
            decryByte = decrypt(Hex.decode("043e4b857685dde4930b8fdad58b229bac2c12a464104409fd23200b0e08df895d365f90bb7d56b2fdc17edd2d3bca5e034c8a43e4d0dc082101eced517ef995393f70769a790412facf5d0d29e18107049a57977a254f71a698b70d784419cccbcd14f9f30f113ff45790"), Encodes.decodeBase64(privateKeyBase64));
            System.out.println("使用base64私钥解密，net hex密文=" + new String(decryByte));

            encrypt = encrypt((BCECPublicKey) getBCECPublicKeyFromD(strPubKey), strBody.getBytes(StandardCharsets.UTF_8));
            base64Encrypt = Encodes.encodeBase64(encrypt);
            System.out.println("使用hex公钥加密，base64密文=" + base64Encrypt);
            System.out.println("使用hex公钥加密，hex密文=" + Hex.toHexString(encrypt));
            decryByte = decrypt(getPrivatekeyFromD(strPrivateKey), Encodes.decodeBase64(base64Encrypt));
            System.out.println("使用hex私钥解base64密文=" + new String(decryByte));
            decryByte = decrypt(getPrivatekeyFromD(strPrivateKey), Hex.decode(Hex.toHexString(encrypt)));
            System.out.println("使用hex私钥解hex密文=" + new String(decryByte));
            //使用base64私钥 解密 base64 密文
            baseEncrypt = "BLgKi+y39r1J6+BHbrKXu2hdmBzyjVTSzNAxPwyLhZZH6gdes67B/O5c5J3rqS+cmGFnvoxsNTgi8d5GrQmlFEB2hl9LQipNU4BxRiC4wlFULOZpCetT/G/xQGRw1oli+rwgi3r8RIOH5hk=";
            decryByte = decrypt(getPrivatekeyFromD(strPrivateKey), Encodes.decodeBase64(baseEncrypt));
            System.out.println("使用hex私钥解密，net base64密文=" + new String(decryByte));
            decryByte = decrypt(getPrivatekeyFromD(strPrivateKey), Hex.decode("043e4b857685dde4930b8fdad58b229bac2c12a464104409fd23200b0e08df895d365f90bb7d56b2fdc17edd2d3bca5e034c8a43e4d0dc082101eced517ef995393f70769a790412facf5d0d29e18107049a57977a254f71a698b70d784419cccbcd14f9f30f113ff45790"));
            System.out.println("使用hex私钥解密，net hex密文=" + new String(decryByte));

            String algorithm = "SM3withSM2";
            String pfxFile = "C:\\Users\\DELL\\Desktop\\临时\\gmcert_大锤科技_21082417042785.p12";//sm2证书对应的pfx
            String password = "123456"; //pfx访问密码
            Security.addProvider(new BouncyCastleProvider());
            FileInputStream fis = new FileInputStream(pfxFile);
            KeyStore ks2 = KeyStore.getInstance("PKCS12", "BC");
            ks2.load(fis, password.toCharArray());
            Enumeration enum1 = ks2.aliases();
            String keyAlias = null;
            if (enum1.hasMoreElements()) {
                keyAlias = (String) enum1.nextElement();
            }
            Signature sig = Signature.getInstance(algorithm, "BC");
            BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) ks2.getKey(keyAlias, null);
            Certificate certEntry = ks2.getCertificate(keyAlias);
            BCECPublicKey pubKey1 = (BCECPublicKey) certEntry.getPublicKey();

            //使用私钥证书生成base64签名
            byte[] priKeyData = readFile("C:\\Users\\DELL\\Desktop\\临时\\gmcert_小二黑科技_21082417012770\\小二黑科技.cert.pem");
            //bcecPrivateKey= convertSEC1ToECPrivateKey(priKeyData);
            X509Certificate cert = getX509Certificate("C:\\Users\\DELL\\Desktop\\临时\\gmcert_小二黑科技_21082417012770\\小二黑科技.cert.pem");
            BCECPublicKey pubKey = getBCECPublicKey(cert);
            System.out.println("读取公钥证书，base64公钥=" + Encodes.encodeBase64(pubKey.getEncoded()));
            System.out.println("读取私钥证书，base64私钥=" + Encodes.encodeBase64(bcecPrivateKey.getEncoded()));
            System.out.println("读取私钥证书对应的公钥，base64公钥=" + Encodes.encodeBase64(pubKey1.getEncoded()));

            encrypt = encrypt((BCECPublicKey) string2PublicKey(Encodes.encodeBase64(pubKey.getEncoded())), strBody.getBytes(StandardCharsets.UTF_8));
            base64Encrypt = Encodes.encodeBase64(encrypt);
            System.out.println("读取公钥证书，base64密文=" + base64Encrypt);

            encrypt = encrypt((BCECPublicKey) string2PublicKey(Encodes.encodeBase64(pubKey1.getEncoded())), strBody.getBytes(StandardCharsets.UTF_8));
            base64Encrypt = Encodes.encodeBase64(encrypt);
            System.out.println("使用私钥证书对应的公钥加密，base64密文=" + base64Encrypt);
            encrypt = decrypt((BCECPrivateKey) string2PrivateKey(Encodes.encodeBase64(bcecPrivateKey.getEncoded())), encrypt);
            System.out.println("读取私钥证书，解密base64密文=" + new String(encrypt));

        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     * 十六进制串转化为byte数组
     *
     * @return the array of byte
     */
    public static byte[] hexToByte(String hex)
            throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        }
        char[] arr = hex.toCharArray();
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
            String swap = "" + arr[i++] + arr[i];
            int byteint = Integer.parseInt(swap, 16) & 0xFF;
            b[j] = new Integer(byteint).byteValue();
        }
        return b;
    }

    public static String getNow() {
        String formatStr = "yyyy-MM-dd HH:mm:ss.SSS";
        LocalDateTime lnow = LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(formatStr);
        String nowFormat = lnow.format(dateTimeFormatter);
        return nowFormat;
    }

}